package arcane.object.game.sprite;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import org.lwjgl.opengl.GL11;

import com.golden.gamedev.object.Sprite;

public class GeometricSprite extends Sprite {
	/**
	 * This sprite's angle
	 */
	protected double angle;
	
	/**
	 * This sprite's speed
	 */
	protected double speed;
	
	/**
	 * The x offset this sprite is rendered at. (for rotational purposes.)
	 */
	protected int originX;
	
	/**
	 * The y offset this sprite is rendered at. (for rotation purposes.)
	 */
	protected int originY;

	/**
	 * This sprite's rotation in degrees per millisecond.
	 */
	protected float angleInc;

	/**
	 * Alpha value of sprite's texture
	 */
	protected float alpha;

	/**
	 * Amount to increment alpha by each update
	 */
	protected float alphaInc;

	/**
	 * Limits alpha value to a maximum
	 */
	protected float maxAlpha;

	/**
	 * Limits alpha value to a minimum
	 */
	protected float minAlpha;

	/**
	 * Color to apply to sprite's texture
	 */
	protected Color color = Color.WHITE;
	
	// Flags for making sprite "glow" by altering alpha transparency
	private boolean toggleAlpha;
	private boolean decAlpha;
	private boolean incAlpha;
	
	//Used in calculating this sprite's bounding rectangle.
	private static Rectangle bounds = new Rectangle();

	/**
	 * Constructs a new GeometricSprite at location (0,0) with a specified
	 * image.
	 * 
	 * @param img
	 *            specified image
	 */
	public GeometricSprite(BufferedImage img) {
		this(img, 0, 0);
	}

	/**
	 * Constructs a new GeometricSprite at location (x,y) with no image.
	 * 
	 * @param x
	 *            x location of sprite
	 * @param y
	 *            y location of sprite
	 */
	public GeometricSprite(double x, double y) {
		this(null, x, y);
	}

	/**
	 * Constructs a new GeometricSprite with a specified image and location.
	 * 
	 * @param imgs
	 *            specified image
	 * @param x
	 *            specified x location
	 * @param y
	 *            specified y location
	 */
	public GeometricSprite(BufferedImage imgs, double x, double y) {
		super(imgs, x, y);
		if (imgs != null) {
			originX = -imgs.getWidth() >> 1;
			originY = -imgs.getHeight() >> 1;
		}
		alpha = 1;
		toggleAlpha = false;
		alphaInc = 0;
		incAlpha = true;
		decAlpha = false;
		minAlpha = maxAlpha = 1;

		angleInc = 0;
	}

	/**
	 * Sets this GeometricSprite's width. Notice that the width may not
	 * correspond to the sprite's image width.
	 * 
	 * @param w
	 *            sprite's new width
	 */
	public void setWidth(int w) {
		this.width = w;
		originX = -w >> 1;
	}
	
	public void setSize(int w, int h){
		setWidth(w);
		setHeight(h);
	}

	/**
	 * Sets this GeometricSprite's height. Notice that the height may not
	 * correspond to the sprite's image height.
	 * 
	 * @param h
	 *            sprites new height
	 */
	public void setHeight(int h) {
		this.height = h;
		originY = -h >> 1;
	}

	/**
	 * Sets the rotation point for this GeomtricSprite. The rotation point is
	 * the point from which this sprite is rotated.
	 * 
	 * @param x
	 *            new x rotation point
	 * @param y
	 *            new y rotation point
	 */
	public void setRotationPoint(int x, int y) {
		originX = x;
		originY = y;
	}

	/**
	 * Sets the rotation point for this GeomtricSprite. The rotation point is
	 * the point from which this sprite is rotated.
	 * 
	 * @param p
	 *            sprite's new rotation point
	 */
	public void setRotationPoint(Point p) {
		setRotationPoint(p.x, p.y);
	}

	/**
	 * Convenience method for setting up sprite's who's alpha values are to
	 * shift. Sprite's alpha values will toggle after calling this method.
	 * 
	 * @param increment
	 *            sprite's alpha increment
	 * @param min
	 *            sprite's minimum alpha value
	 * @param max
	 *            sprite's maximum alpha value
	 */
	public void setAlphaRange(float increment, float min, float max) {
		setDeltaAlpha(increment);
		setAlphaMinimum(min);
		setAlphaMaximum(max);
		toggleAlpha = true;
	}

	/**
	 * Sets the maximum value this sprite's alpha value can reach through
	 * updating. The sprite's alpha value may be set higher than its maximum
	 * value with "setAlpha(...)"
	 * 
	 * @param max
	 *            sprite's new maximum alpha value
	 */
	public void setAlphaMaximum(float max) {
		if (max > 1 || max < 0) {
			throw new IllegalArgumentException(
					"Alpha value maximum for Sprite must be between 0 and 1");
		} else
			maxAlpha = max;
	}

	/**
	 * Sets the minimum value this Sprite's alpha value can reach through
	 * updating.
	 * 
	 * @param min
	 *            sprite's new minimum alpha value
	 */
	public void setAlphaMinimum(float min) {
		if (min < 0 || min > 1) {
			throw new IllegalArgumentException(
					"Alpha value minimum for Sprite must be between 0 and 1");
		} else
			minAlpha = min;
	}

	/**
	 * Sets the amount this sprite's alpha value is increased by each
	 * millisecond.
	 * 
	 * @param increment
	 *            delta alpha value for this sprite
	 */
	public void setDeltaAlpha(float increment) {
		if (increment < 0 || increment > 1) {
			throw new IllegalArgumentException(
					"Increment for Sprite alpha value must be between 0 and 1");
		}
		this.alphaInc = increment;
	}

	/**
	 * Determines whether this sprite continually shifts between its max and min
	 * alpha values, or simply grows to reach its maximum from its minimum.
	 * 
	 * @param toggle
	 *            sprite's new alpha rate behavior
	 */
	public void setAlphaToggle(boolean toggle) {
		this.toggleAlpha = toggle;
	}

	/**
	 * Updates this sprite's 2 dimensional motion, rotation, and alpha values.
	 */
	public void update(long time) {
		super.update(time);
		if (isActive()) {
			angle += (angleInc * time);
			// Toggle alpha from min/max
			if (toggleAlpha) {
				if (incAlpha) {
					this.alpha += alphaInc;
					if (alpha >= maxAlpha) {
						alpha = maxAlpha;
						decAlpha = true;
						incAlpha = false;
					}
				} else if (decAlpha) {
					this.alpha -= alphaInc;
					if (alpha <= minAlpha) {
						alpha = minAlpha;
						decAlpha = false;
						incAlpha = true;
					}
				}
			}
			// Get alpha to max and stop.
			else {
				this.alpha += alphaInc;
				if (alpha >= maxAlpha) {
					alpha = maxAlpha;
					decAlpha = true;
					incAlpha = false;
				}
			}
		}
	}

	/**
	 * Sets this sprite's speed and angle of direction.
	 */
	public void setMovement(double speed, double angle) {
		super.setMovement(speed, angle);
		this.speed = speed;
		this.angle = angle;
	}

	/**
	 * Returns the speed of this sprite
	 * 
	 * @return speed of this sprite
	 */
	public double getSpeed() {
		return speed;
	}

	/**
	 * Sets the color this sprite's texture is rendered with.
	 * 
	 * @param color
	 *            color sprite is rendered with
	 */
	public void setColor(Color color) {
		this.color = color;
	}

	/**
	 * Returns the bounding rectangle of this sprite. Note because the width and
	 * height do not necessarily reflect the sprite's dimensions, this bounding
	 * rectangle many not coincide with the sprite's dimension in pixels.
	 * 
	 * @return bounding rectangle of this sprite
	 */
	public Rectangle getBounds() {
		bounds.setFrame(getScreenX(), getScreenY(), width, height);
		return bounds;
	}

	/**
	 * Returns the color this sprite's texture is rendered with.
	 * 
	 * @return sprite's texture color
	 */
	public Color getColor() {
		return color;
	}

	/**
	 * Sets this sprite's opacity.
	 * 
	 * @param alpha
	 *            this sprite's new opacity
	 */
	public void setAlpha(float alpha) {
		this.alpha = alpha;
	}

	/**
	 * Returns this sprite's opacity. 0f indicates the sprite is completely
	 * translucent, 1f indicates the sprite is completely opaque.
	 * 
	 * @return this sprite's alpha value
	 */
	public float getAlpha() {
		return alpha;
	}

	/**
	 * Returns the current angle in degrees this sprite is rendered at.
	 * 
	 * @return this sprite's current angle
	 */
	public double getAngle() {
		return angle;
	}

	/**
	 * Returns the amount of degrees per millisecond this sprite rotates.
	 * 
	 * @return degrees per millisecond this sprite rotates
	 */
	public float getAngleIncrement() {
		return angleInc;
	}

	/**
	 * Sets the amount this Sprite's angle is shifted each update. This amount
	 * is calculated in relation to time in milliseconds. Simply, this value is
	 * the amount of degrees per millisecond.
	 * 
	 * @param dAng
	 *            degrees per millisecond this sprite rotates.
	 */
	public void setAngleIncrement(float dAng) {
		angleInc = dAng;
	}

	/**
	 * Sets the angle this Sprite will be rotated to before rendering.
	 * 
	 * @param ang
	 *            sprite's angle of rotation to be rendered at.
	 */
	public void setAngle(double ang) {
		this.angle = ang;
	}

	public void setSpeed(double s) {
		this.speed = s;
	}

	/**
	 * Returns the center x coordinate of this sprite.
	 * 
	 * @return center x coordinate of this sprite.
	 */
	public int getCenterX() {
		return (int) getScreenX() + (width >> 1);
	}

	/**
	 * Returns the center y coordinate of this sprite.
	 * 
	 * @return center y coordinate of this sprite.
	 */
	public int getCenterY() {
		return (int) getScreenY() + (height >> 1);
	}

	/**
	 * Translates rendering context to the center of the sprite, and rotates
	 * context by the number of degrees this sprite is at.
	 */
	protected void translate() {
		GL11.glPushMatrix();
		GL11.glTranslated(getCenterX(), getCenterY(), 0);
		GL11.glRotatef((float) angle, 0, 0, 1);
	}

	/**
	 * If this sprite is active, this method renders it at a specific location.
	 */
	public void render(Graphics2D g, int x, int y) {
		if (isActive() && getImage() != null) {
			translate();
			GL11.glColor4f((float) color.getRed() / 255, (float) color
					.getGreen() / 255, (float) color.getBlue() / 255, alpha);
			g.drawImage(getImage(), originX, originY, width, height, null);
			GL11.glPopMatrix();
		}
	}
}
